/*
 * =====================================================================================
 *
 *       Filename:  config.cc
 *
 *    Description:  configuration acquiry and store.
 *
 *        Version:  1.0
 *        Created:  09/08/2020 10:02:05 PM
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  qiulu, choulu@jd.com
 *        Company:  JD.com, Inc.
 *
 * =====================================================================================
 */
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <stdio.h>
#include <pthread.h>
#include <sys/mman.h>

#include "mem_check.h"
#include "config.h"
#include "log.h"

int str2int(const char *strval, int def)
{
	int ret_code = def;
	if (isdigit(strval[0]) || (strval[0] == '-' && isdigit(strval[1])))
		return atoi(strval);

	if (!strcasecmp(strval, "On"))
		ret_code = 1;
	else if (!strcasecmp(strval, "Off"))
		ret_code = 0;
	else if (!strcasecmp(strval, "Yes"))
		ret_code = 1;
	else if (!strcasecmp(strval, "No"))
		ret_code = 0;
	else if (!strcasecmp(strval, "True"))
		ret_code = 1;
	else if (!strcasecmp(strval, "False"))
		ret_code = 0;
	else if (!strcasecmp(strval, "Enable"))
		ret_code = 1;
	else if (!strcasecmp(strval, "Disable"))
		ret_code = 0;
	else if (!strcasecmp(strval, "Enabled"))
		ret_code = 1;
	else if (!strcasecmp(strval, "Disabled"))
		ret_code = 0;

	return ret_code;
}

void DTCConfig::Dump(FILE *fp, bool dec)
{
	fprintf(fp, "##### ORIGINAL FILE %s #####\n", filename.c_str());
	for (secmap_t::iterator i = smap.begin(); i != smap.end(); i++)
	{
		const char *sec = i->first.c_str();
		keymap_t &kmap = i->second;
		if (kmap.size() <= 0)
			continue;
		fprintf(fp, "\n[%s]\n", sec);
		for (keymap_t::iterator j = kmap.begin(); j != kmap.end(); j++)
		{
			const char *key = j->first.c_str();
			const char *val = j->second.c_str();
			char flag = val[0];
			if (flag == '\0')
			{
				if (dec == true)
					fprintf(fp, "# %s NOT SET\n", key);
			}
			else
			{
				val++;
				if (flag == ' ' || dec == false)
					fprintf(fp, "%s = %s\n", key, val);
				else
					fprintf(fp, "%c %s = %s\n", flag, key, val);
			}
		}
	}
}

int DTCConfig::Dump(const char *fn, bool dec)
{
	FILE *fp = fopen(fn, "w");
	if (fp == NULL)
		return -1;
	Dump(fp, dec);
	fclose(fp);
	return 0;
}

static char *skip_blank(char *p)
{
	while (isblank(*p))
		p++;
	return p;
}

int DTCConfig::parse_buffered_config(char *buf, const char *fn, const char *defsec, bool bakconfig)
{
	int len = strlen(buf), ln = 0, ret_code = 0;
	char *start = buf, *end = buf;

	if (defsec == NULL)
		defsec = "";
	if (fn == NULL)
		fn = "buffered config";
	keymap_t *kmap = &smap[defsec];

	for (start = buf, ln = 0; end && buf + len > start; start = end + 1)
	{
		end = strchr(start, '\n');
		if (end)
		{
			if (*end)
				*end = '\0';
			if (end > start && end[-1] == '\r')
				end[-1] = '\0';
		}

		std::string val("?");
		keymap_t *m = kmap;

		ln++;

		char *v = NULL;
		char *key = skip_blank(start);
		// blank or comment line
		if (key[0] == '\0' || key[0] == '#' || key[0] == '?' || key[0] == ';')
			continue;
		// key must printable
		if (!isprint(key[0]))
		{
			log_warning("%s(%d): invalid line: %s", fn, ln, key);
			ret_code = -1;
			continue;
		}

		// find the equation
		start = key + strcspn(key, "= \t");
		if (*start != '\0')
		{
			char *p = start[0] == '=' ? start : skip_blank(start + 1);
			if (*p == '=')
			{
				v = skip_blank(p + 1);
			}
			else if (key[0] == '[')
				/*OK*/;
			else
			{
				log_warning("%s(%d): invalid line: %s", fn, ln, key);
				ret_code = -1;
				continue;
			}
			*start = '\0';
		}
		else if (key[0] != '[')
		{
			log_warning("%s(%d): invalid line: %s", fn, ln, key);
			ret_code = -1;
			continue;
		}

		if (key[0] == '[')
		{
			char *r = strchr(key, ']');
			if (!r)
			{
				log_warning("%s(%d): invalid section: %s", fn, ln, key);
				ret_code = -1;
				continue;
			}
			else if (!((r[1] == '\0' && v == NULL) || (r[1] == '.' && v != NULL)))
			{
				log_warning("%s(%d): invalid section: %s", fn, ln, key);
				ret_code = -1;
				continue;
			}
			else
			{
				*r = '\0';
				m = &smap[key + 1];
				if (r[1] == '\0')
				{
					kmap = m;
					continue;
				}
				key = r + 2;
			}
		}

		if (v == NULL)
			continue;

		switch (v[0])
		{
		case '(':
			start = strchr(v, ')');
			if (start == NULL)
				goto error;
			break;
		case '[':
			start = strchr(v, ']');
			if (start == NULL)
				goto error;
			break;
		case '{':
			start = strchr(v, '}');
			if (start == NULL)
				goto error;
			break;

		case '"':
			start = strrchr(v + 1, '"');
			if (start == NULL)
				goto error;
			break;
		case '\'':
			start = strrchr(v + 1, '\'');
			if (start == NULL)
				goto error;
			break;
		default:
			start = end ? end - 1 : v + strlen(v) - 1;
			if (*start == '\0')
				start--;
			while (start > v && isblank(*start))
				start--;
			break;
		error:
			log_warning("%s(%d): unended quote or bracket", fn, ln);
			ret_code = -1;
			continue;
		}
		start[1] = '\0';

		if (v[0])
			val.append(v);

		(*m)[key] = val;
	}

	if (bakconfig)
	{
		char bak_config[1024];
		int err;
		snprintf(bak_config, sizeof(bak_config), "cp %s ../stat/", fn);
		err = system(bak_config);
	}
	return ret_code;
}

int DTCConfig::parse_config(const char *fn, const char *defsec, bool bakconfig)
{
	char *buf;
	int len, fd, ret_code = -1;

	if (fn == NULL)
		fn = filename.c_str();
	else if (filename.size() <= 0)
	{
		filename = fn;
	}
	if (filename[0] == '\0')
		return -1;

	fd = open(fn, O_RDONLY);
	if (fd < 0)
	{
		log_warning("open %s error, cwd=%s, %m\n", fn, get_current_dir_name());
		return -1;
	}
	len = lseek(fd, 0L, SEEK_END);
	lseek(fd, 0L, SEEK_SET);
	buf = (char *)MALLOC(len + 1);
	int unused;
	unused = read(fd, buf, len);
	buf[len] = '\0';
	ret_code = len + 1;
	close(fd);
	ret_code = parse_buffered_config(buf, fn, defsec, bakconfig);
	FREE(buf);
	return ret_code;
}

bool DTCConfig::has_section(const char *sec)
{
	secmap_t::iterator n = smap.find(sec);
	return n != smap.end() && n->second.size() > 0 ? true : false;
}

bool DTCConfig::has_key(const char *sec, const char *key)
{
	secmap_t::iterator n = smap.find(sec);
	if (n == smap.end())
		return false;
	keymap_t &kmap = n->second;
	keymap_t::iterator m = kmap.find(key);
	return m != kmap.end() && m->second.size() > 0 ? true : false;
}

const char *DTCConfig::get_str_val(const char *sec, const char *key)
{
	keymap_t &kmap = smap[sec];
	std::string &v = kmap[key];
	if (v.size() == 0)
		return NULL;
	v[0] = ' ';
	return v.c_str() + 1;
}

int DTCConfig::get_int_val(const char *sec, const char *key, int def)
{
	const char *val = get_str_val(sec, key);
	if (val == NULL)
		return def;

	return str2int(val, def);
}

unsigned long long DTCConfig::get_size_val(
	const char *sec,
	const char *key,
	unsigned long long def,
	char unit)
{
	const char *val = get_str_val(sec, key);
	if (val == NULL || !isdigit(val[0]))
		return def;

	const char *p;
	double a = strtod(val, (char **)&p);
	if (*p)
		unit = *p;
	switch (unit)
	{
	case 'b':
		break;
	case 'B':
		break;
	case 'k':
		a *= 1000;
		break;
	case 'K':
		a *= 1 << 10;
		break;
	case 'm':
		a *= 1000000;
		break;
	case 'M':
		a *= 1 << 20;
		break;
	case 'g':
		a *= 1000000000;
		break;
	case 'G':
		a *= 1 << 30;
		break;
	case 't':
		a *= 1000000000000LL;
		break;
	case 'T':
		a *= 1ULL << 40;
		break;
	}

	return (unsigned long long)a;
}

int DTCConfig::get_idx_val(
	const char *sec, const char *key,
	const char *const *array, int nDefault)
{
	const char *val = get_str_val(sec, key);
	if (val == NULL)
		return nDefault;

	if (isdigit(val[0]))
	{
		char *p;
		int n = strtol(val, &p, 0);
		if (*p == '\0')
		{
			for (int i = 0; array[i]; i++)
			{
				if (n == i)
					return i;
			}
		}
	}

	for (int i = 0; array[i]; i++)
	{
		if (!strcasecmp(val, array[i]))
			return i;
	}
	return -1;
}

class AutoConfigSection : public AutoConfig
{
private:
	static pthread_mutex_t glock;
	void GlobalLock(void) { pthread_mutex_lock(&glock); }
	void GlobalUnlock(void) { pthread_mutex_unlock(&glock); }

private:
	DTCConfig *parent;
	char *section;
	// buf must have enough room place composed key name
	char buf[256];
	char *last;

public:
	AutoConfigSection(DTCConfig *p, const char *sec);
	~AutoConfigSection();

	virtual int get_int_val(const char *key, const char *inst, int def = 0);
	virtual unsigned long long get_size_val(const char *key, const char *inst, unsigned long long def = 0, char unit = 0);
	virtual int get_idx_val(const char *, const char *, const char *const *, int = 0);
	virtual const char *get_str_val(const char *key, const char *inst);

private:
	// return composed key, or vanilla key, always non-null
	const char *findkey(const char *key, const char *inst);
	// strip suffix digits
	int stripnumber(void);
	// strip suffix alphaphetic
	int stripalpha(void);
	// strip suffix punct
	int strippunct(void);
};

pthread_mutex_t AutoConfigSection::glock = PTHREAD_MUTEX_INITIALIZER;

AutoConfigSection::AutoConfigSection(DTCConfig *p, const char *sec)
{
	this->parent = p;
	this->section = STRDUP(sec);
}

AutoConfigSection::~AutoConfigSection()
{
	FREE_CLEAR(section);
}

int AutoConfigSection::stripnumber(void)
{
	int n = 0;
	while (last >= buf && isdigit(*last))
	{
		last--;
		n++;
	}
	last[1] = 0;
	strippunct();
	return n;
}

int AutoConfigSection::stripalpha(void)
{
	int n = 0;
	while (last >= buf && isalpha(*last))
	{
		last--;
		n++;
	}
	last[1] = 0;
	strippunct();
	return n;
}

int AutoConfigSection::strippunct(void)
{
	int n = 0;
	while (last >= buf && *last != '@' && !isalnum(*last))
	{
		last--;
		n++;
	}
	last[1] = 0;
	return n;
}

const char *AutoConfigSection::findkey(const char *key, const char *inst)
{
	snprintf(buf, sizeof(buf), "%s@%s", key, inst);
	last = buf + strlen(buf) - 1;
	strippunct();

	do
	{
		if (parent->get_str_val(section, buf) != NULL)
		{
			return buf;
		}
	} while (isdigit(*last) ? stripnumber() : stripalpha());

	return key;
}

int AutoConfigSection::get_int_val(const char *key, const char *inst, int def)
{
	int ret;
	GlobalLock();
	ret = parent->get_int_val(section, findkey(key, inst), def);
	GlobalUnlock();
	return ret;
}
unsigned long long AutoConfigSection::get_size_val(const char *key, const char *inst, unsigned long long def, char unit)
{
	unsigned long long ret;
	GlobalLock();
	ret = parent->get_size_val(section, findkey(key, inst), def, unit);
	GlobalUnlock();
	return ret;
}
int AutoConfigSection::get_idx_val(const char *key, const char *inst, const char *const *idxval, int def)
{
	int ret;
	GlobalLock();
	ret = parent->get_idx_val(section, findkey(key, inst), idxval, def);
	GlobalUnlock();
	return ret;
}
const char *AutoConfigSection::get_str_val(const char *key, const char *inst)
{
	const char *ret;
	GlobalLock();
	ret = parent->get_str_val(section, findkey(key, inst));
	GlobalUnlock();
	return ret;
}

AutoConfig *DTCConfig::get_auto_config_instance(const char *section)
{
	AutoConfigSection *inst;
	NEW(AutoConfigSection(this, section), inst);
	return inst;
}
